import * as debug from 'debug'
import * as moment from 'moment'
import request from '../request'
import { RedisStore } from '../redisstore'
import { charSalt } from '../crypto'
import * as qs from 'querystring'

const log = debug('cnipr-sdk')

export const cniprOptions = {
  clientId: 'ABEC0BD17688BC6F4DEDA73E07B2B18B',
  clientSecret: '72D242D2448ED0899E66B9B4C4343716',
  redirectUri: 'https://www.aipatn.com/oauth2/callback',
  refreshToken: '458a98a71efc6f09a9c765b297d58472',
  openId: 'e3504f932dd828f21fc394a2bb011ff4',
  openKey: '913ef58fc002eb2da04ca94448b04bf5',
}

// interface OAuth2Response {
//   status: number
//   message: string
//   expires_in: number
//   refresh_token: string
//   access_token: string
// }

export class AccessToken {
  accessToken: string = ''
  expireIn: number = 0 // 秒
  expireTime: number = 0 // Unix时间戳(秒)

  set(token: any) {
    if (token.access_token) {
      this.accessToken = token.access_token || ''
      this.expireIn = token.expires_in || 0
      this.expireTime = moment().unix() + this.expireIn
    }

    if (token.accessToken) {
      this.accessToken = token.accessToken || ''
      this.expireIn = token.expireIn || 0
      this.expireTime = token.expireTime || 0
    }
  }

  /*!
   * 检查AccessToken是否有效，检查规则为当前时间和过期时间进行对比
   * Examples:
   * ```
   * token.isExpired();
   * ```
   */
  isNotExpired() {
    return !!this.accessToken && (moment().unix() < this.expireTime - 1800)
  }

}

export class CniprHelper {
  // 固定参数
  options: any

  // 动态参数
  authorizationCode: string = ''

  defaultUrlPrefix: string = 'https://open.cnipr.com/'

  store: RedisStore = new RedisStore()
  accessTokenKey: string = 'CNIPR_ACCESS_TOKEN'

  constructor(opts?: any) {
    this.options = Object.assign({}, cniprOptions, opts || {})
  }

  /**
   * Step1：获取Authorization Code
   * API说明：https://open.cnipr.com/oauth2/doc/oauth2/authorization
   *
   * 请求地址：https://open.cnipr.com/oauth2/authorize
   * 请求方法：GET
   * 请求参数：
   * 参数字段	是否必须	说明
   * client_id	必须	用户登录后，接入应用成功后，分配的client_id。
   * redirect_uri	必须	成功授权后的回调地址，必须跟注册client_id时填写的地址一致。
   * response_type	必须	授权类型，此值固定为"code"
   * state	可选	用于保持请求和回调的状态，授权请求成功后原样带回给第三方。
   * 该参数用于防止csrf攻击（跨站请求伪造攻击），强烈建议第三方带上该参数。
   * 响应数据包格式：
   * 如果授权成功，授权服务器会将用户的浏览器重定向到redirect_uri，并带上code，openid和openkey等参数。例子如下：
   * http:/www.myurl.com/example?code=CODE&openid=OPENID&openkey=OPENKEY
   *
   * 返回参数：
   * 参数字段	说明
   * code	用来换取access_token的授权码，有效期为10分钟
   * openid	用户统一标识，可以唯一标识一个用户。网站或应用可将此ID进行存储，便于用户下次登录时辨识其身份。
   * openkey	与openid对应的用户key，是验证openid身份的验证密钥
   *
   * 错误码说明：接口调用有错误时，会返回code和msg字段，以Json格式返回。具体参见Cnipr-OAuth2返回值码说明
   *
   */
  async getAuthorizationCode() {
    const state: string = 'AIPATN_' + charSalt()
    const url = this.defaultUrlPrefix + 'oauth2/authorize'
    try {
      const resp = await request.get(
        url,
        {
          params: {
            client_id: this.options.clientId,
            redirect_uri: encodeURI(this.options.redirectUri),
            response_type: 'code',
            state
          }
        })
      if (resp.data) {
        const data = resp.data
        if (data.code) {
          const err = new Error(data.msg)
          err.name = 'CniprAPIError'
          throw err
        }
      }
    } catch (e) {
      if (e.code == 'ECONNABORTED') {
      } else {
        throw e
      }
    }
  }

  /**
   * Step2：通过Authorization Code获取Access Token
   *
   * 请求地址：https://open.cnipr.com/oauth2/access_token
   * 请求方法：GET
   * 请求参数：
   * 参数字段	是否必须	说明
   * client_id	必须	应用注册时分配的client_id
   * client_secret	必须	应用注册时分配的client_secret
   * redirect_uri	必须	成功授权后的回调地址，必须跟注册client_id时填写的地址一致。
   * grant_type	必须	授权类型，此固定值为authorization_code
   * code	必须	通过上面第一步所获得的Authorization Code
   * state	可选	用于保持请求和回调的状态，授权请求成功后原样带回给第三方。
   * 该参数用于防止csrf攻击（跨站请求伪造攻击），强烈建议第三方带上该参数。
   *
   * 响应数据包格式：
   * 如果请求成功，授权服务器会返回JSON格式的字符串，如下：
   * {
   * "status":STATUS,
   * "message":MESSAGE,
   * "expires_in":EXPIRES_IN,
   * "refresh_token":REFRESH_TOKEN,
   * "access_token":ACCESS_TOKEN
   * }
   *
   * 字符串参数说明：
   * 参数字段	说明
   * access_token	访问资源API的凭证
   * refresh_token	请求刷新令牌的凭证
   * status	请求返回状态码，具体见附录
   * message	请求返回信息描述
   * expires_in	access_token的过期时长，以秒为单位
   *
   * 错误码说明：接口调用有错误时，会返回code和msg字段，以Json格式返回。具体参见Cnipr-OAuth2返回值码说明
   */
  async getAccessToken(code: string) {
    const state: string = 'AIPATN_' + charSalt()
    const url = this.defaultUrlPrefix + 'oauth2/access_token'
    try {
      const resp = await request.get(
        url,
        {
          params: {
            client_id: this.options.clientId,
            client_secret: this.options.clientSecret,
            redirect_uri: encodeURI(this.options.redirectUri),
            grant_type: 'authorization_code',
            code,
            state
          }
        })
      if (resp.data) {
        const data = resp.data
        if (data.code) {
          const err = new Error(data.msg)
          err.name = 'CniprAPIError'
          throw err
        }
      }
    } catch (e) {
      if (e.code == 'ECONNABORTED') {
      } else {
        throw e
      }
    }
  }

  /**
   * 简介：对于在Cnipr开放平台注册并申请授权权限的应用，采用Authorization Code获取Access Token，
   * 会拿到一个有效期为30个自然日的Access Token和无限时长的Refresh Token。若是Access Token过期，
   * 应用就可以使用Refresh Token刷新请求以获得新的Access Token。
   *
   * 注意：
   * 使用使用 Implicit Grant 方式获取 Access Token，返回结果中无 Refresh Token。此模式下，若Access Token过期，再次申请即可。
   *
   * 请求步骤：
   *
   * 请求地址：https://open.cnipr.com/oauth2/access_token
   * 请求方法：GET
   * 请求参数：
   * 参数字段	是否必须	说明
   * client_id	必须	用户登录后，接入应用成功后，分配的client_id。
   * client_secret	必须	应用注册时分配的clientSecret
   * redirect_uri	必须	授权回调地址，必须和应用注册的地址一致
   * grant_type	必须	请求类型，此处固定为"refresh_token"
   * refresh_token	必须	请求授权成功时获取的refresh_token
   * state	可选	用于保持请求和回调的状态，授权请求成功后原样带回给第三方。
   * 该参数用于防止csrf攻击（跨站请求伪造攻击），强烈建议第三方带上该参数。
   *
   * 响应数据包格式：
   * 如果请求成功，授权服务器会返回JSON格式的字符串，如下：
   * {
   * "status":STATUS,
   * "message":MESSAGE,
   * "expires_in":EXPIRES_IN,
   * "refresh_token":REFRESH_TOKEN,
   * "access_token":ACCESS_TOKEN
   * }
   *
   * 返回参数：
   * 参数字段	说明
   * access_token	访问资源API的凭证
   * refresh_token	请求刷新令牌的凭证
   * status	请求返回状态码，具体见Cnipr-OAuth2返回值码说明
   * message	请求返回信息描述
   * expires_in	access_token的过期时长，以秒为单位
   *
   * 错误码说明：
   * 接口调用有错误时，会返回status和message字段。具体参见Cnipr-OAuth2返回值码说明
   *
   */
  async refreshAccessToken() {
    const state: string = 'AIPATN_' + charSalt()
    const url = this.defaultUrlPrefix + 'oauth2/access_token'
    try {
      const resp = await request.get(
        url,
        {
          params: {
            client_id: this.options.clientId,
            client_secret: this.options.clientSecret,
            redirect_uri: encodeURI(this.options.redirectUri),
            grant_type: 'refresh_token',
            refresh_token: this.options.refreshToken,
            state
          }
        })
      if (resp.data) {
        const data = resp.data
        log('Request:\r\n%s\r\nResponse:\r\n%o', url, data)
        if (data.status === 0) {
          const token = new AccessToken()
          token.set(data)
          return token
        } else {
          const err = new Error(data.msg)
          err.name = 'CniprAPIError'
          throw err
        }
      }
    } catch (e) {
      if (e.code == 'ECONNABORTED') {
      } else {
        throw e
      }
    }
  }

  async fetchTokenFromStore() {
    const tokenInStore: any = await this.store.get(this.accessTokenKey)
    if (tokenInStore) {
      const token = new AccessToken()
      token.set(tokenInStore)
      return token
    }
    return undefined
  }

  async saveTokenToStore(token?: AccessToken) {
    if (token) {
      await this.store.set(this.accessTokenKey, token, { maxAge: token.expireIn })
    } else {
      await this.store.destroy(this.accessTokenKey)
    }
  }

  async getToken(compulsive: boolean = false) {
    const token: any = await this.fetchTokenFromStore()
    log('access_token in store: %o', token)

    if (!compulsive && !!token && token.isNotExpired()) {
        log('Cnipr Access Token in store is good.')
        return token
    } else {
        const newToken: any = await this.refreshAccessToken()
        log('New access_token: %o', newToken)
        await this.saveTokenToStore(newToken)

        const t: any = await this.fetchTokenFromStore()
        log('access_token in store: %o', t)
        if (!!t && t.isNotExpired()) {
          return t
        }
    }

    return undefined
  }

  /**
   * sf1，专利信息概览检索 *
   * 说明详见：https://open.cnipr.com/oauth2/doc/html?id=sf1
   * curl -X POST http://open.cnipr.com/cnipr-api/rs/api/search/sf1/ABEC0BD17688BC6F4DEDA73E07B2B18B -H 'cache-control: no-cache' -H 'content-type: application/x-www-form-urlencoded' -H 'postman-token: 0dec61ab-730f-6010-7e37-cd7ebf23f2aa'   -d 'dbs=FMZL&exp=%E5%90%8D%E7%A7%B0%3Dcdma&from=0&to=10&openid=e3504f932dd828f21fc394a2bb011ff4&access_token=a8595720c5154154f27b92d40a7cd28a'
   */
  async sf1(searchParams: any) {
    if (!searchParams || !searchParams.exp) {
      throw new Error('无效的请求')
    }

    const token: any = await this.getToken()
    if (!!!token) {
      throw new Error('服务端TOKEN错误！！')
    }

    /**
     * 下列参加中
     * exp 必选
     * dbs、order等 可选
     */
    const exp = searchParams.exp
    const dbs = searchParams.dbs ? searchParams.dbs : 'FMZL,FMSQ,SYXX,WGZL'
    const order = searchParams.order ? searchParams.order : ''
    const option = searchParams.option && parseInt(searchParams.option) < 3 ? searchParams.option : 2
    const from = searchParams.from && !isNaN(searchParams.from) ? searchParams.from : 0
    const to = searchParams.to && !isNaN(searchParams.to) ? searchParams.to : 10
    const displayCols = searchParams.displayCols ? searchParams.displayCols : ''

    // 向cnipr请求数据
    const url: string = this.defaultUrlPrefix + 'cnipr-api/rs/api/search/sf1/' + this.options.clientId

    log('sf1:exp = %s, dbs = %s', exp, dbs)
    const body: any = {
      exp,
      dbs,
      order,
      option,
      from,
      to,
      displayCols,
      openid: this.options.openId,
      access_token: token.accessToken
    }
    log('sf1:body = %o', qs.stringify(body))
    // qs.stringify(body)


    const resp: any = await request.post(
      url,
      qs.stringify(body),
      {
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'cache-control': 'no-cache'
        }
      }
    )

    if (resp) {
      const data: any = resp.data
      log('sf1: response data\r\n%o', data)
      return data
    }

    return undefined
  }

}
